#include <winsock2.h>
#include "MySQL/mysql.h"
#include <vector>

#include "CSemaphore.h"
#include "CTradeHandler.h"
#include "CTradeContainer.h"
#include "CTradeActionPacket.h"
#include "CTradeRequestPacket.h"
#include "CTradeItemAcceptedPacket.h"
#include "CTradeUpdatePacket.h"
#include "CDb.h"
#include "CConsole.h"

CSemaphore * CTradeHandler::tradeListSemaphore;
std::vector<CTrade*> CTradeHandler::tradeList;

CTradeHandler::CTradeHandler() {
}

CTradeHandler::~CTradeHandler() {
	
	tradeList.clear();
}

void CTradeHandler::initialize() 
{
	if (tradeListSemaphore==NULL)
		tradeListSemaphore = new CSemaphore(1);
}

CTrade * CTradeHandler::getTrade(int tradeId) {
	CTrade * tempTrade=NULL;
	waitTradeList();
	for(unsigned int k = 0; k < tradeList.size(); k++) {
		tempTrade = tradeList.at(k);
		if( (tempTrade->getId() == tradeId) && (tradeId > 0) ) {
			return tempTrade;
			break;
		}
	}
	exitTradeList();
	return tempTrade;
}

void CTradeHandler::createTrade(CChar * schar1, CChar * schar2) {
	unsigned int tradeID = 0;
	waitTradeList();
	if(tradeList.size() > 0) {
		CTrade * lastTrade;
		lastTrade = tradeList.back();
		tradeID = (lastTrade->getId() + 1);
		if(tradeID == 0)
			// tradeID always >= 1.  tradeID of 0 for character = character not in a trade
			tradeID = 1;
	}
	else
		tradeID = 1;
	
	CTrade * newTrade = new CTrade;
	newTrade->setId(tradeID);
	newTrade->setChar1(schar1);
	newTrade->setChar2(schar2);

	tradeList.push_back(newTrade);
	exitTradeList();
	CConsole::outDebOnly("Trade %d created",tradeID);
	schar1->setTradeID(tradeID);
	schar2->setTradeID(tradeID);
	schar2->quePacket(CTradeRequestPacket(schar1));
}


void CTradeHandler::deleteTrade(int tradeId) {
	CTrade * trade;
	waitTradeList();
	for(unsigned int k = 0; k < tradeList.size(); k++) {
		trade = tradeList.at(k);
		if( (trade->getId() == tradeId) && (tradeId > 0) ) {
			// Set characters involved in the trade to tradeID 0 (not in a trade)
			CChar * schar1 = trade->getChar1();
			if(schar1 != NULL)
				schar1->setTradeID(0);
			CChar * schar2 = trade->getChar2();
			if(schar2 != NULL)
				schar2->setTradeID(0);
			// Erase trade from trade list
			tradeList.erase(tradeList.begin()+k);
			CConsole::outDebOnly("Trade %d deleted", tradeId);
			break;
		}
	}
	exitTradeList();
}

void CTradeHandler::tradeAction(CChar * schar1, unsigned long action) {
	int tradeID = 0;
	tradeID = schar1->getTradeID();
	CTrade * trade;
	trade = CTradeHandler::getTrade(tradeID);
	if(trade != NULL) {
		CChar * schar2;
		if(schar1 == trade->getChar1()) {
			schar2 = trade->getChar2();
		} else if(schar1 == trade->getChar2()) {
			schar2 = trade->getChar1();
		}

		if(schar2 == NULL) {
			CConsole::outDebOnly("Character null. Deleting trade.");
			deleteTrade(tradeID);
			return;
		}
		else {
			switch(action) {
				case 0x0000:
					CConsole::outDebOnly("Trade request accepted. CharID: %d", schar1->getId());
					schar1->quePacket(CTradeActionPacket(schar2, action));
					schar2->quePacket(CTradeActionPacket(schar1, action));
					break;
				case 0x0001:
					CConsole::outDebOnly("Trade request cancelled. CharID: %d", schar1->getId());
					schar1->quePacket(CTradeActionPacket(schar2, action));
					schar2->quePacket(CTradeActionPacket(schar1, action));

					// Remove trade from trade list
					deleteTrade(tradeID);
					break;
				case 0x0002:
					CConsole::outDebOnly("Trade accepted. CharID: %d", schar1->getId());
					schar2->quePacket(CTradeActionPacket(schar1, action));
					waitTradeList();
					trade->accepted(schar1, true);
					exitTradeList();
					if(trade->isAccepted(schar2)) {
						// Complete the trade
						completeTrade(tradeID);
					}
					break;
			}
		}
	}
	else
		CConsole::outDebOnly("Trade container null");
}

void CTradeHandler::addItemTrade(unsigned short itemID, unsigned long quantity, unsigned char invSlotID, unsigned char tradeSlotID, CChar * schar1)
{
	CItem * item = NULL;
	item = schar1->getItem(LOC_INVENTORY, invSlotID);
	
	//if (invSlotID == 0)
	//	CConsole::outDebOnly("Adding Gil: %d [addItemTrade]",quantity);

	if ((item == NULL) && (invSlotID!=0))
		return;

	unsigned int tradeID = 0;
	tradeID = schar1->getTradeID();
	if(tradeID == 0)
		return;

	CTrade * trade = NULL;
	trade = getTrade(tradeID);
	if(trade == NULL)
		return;

	bool isTradeChar1 = false;
	// schar2 is the character that didnt request the update
	CChar * schar2 = NULL;
	if(schar1 == trade->getChar1()) {
		schar2 = trade->getChar2();
		isTradeChar1 = true;
	} else if(schar1 == trade->getChar2()) {
		schar2 = trade->getChar1();
		isTradeChar1 = false;
	}

	if(schar2 == NULL) {
		CConsole::outDebOnly("Character null. Deleting trade.");
		deleteTrade(tradeID);
		return;
	}

	waitTradeList();
	trade->accepted(schar1, false);

	// if schar1 isTradeChar1, then use Trade Container 1
	if(isTradeChar1) {
		trade->updateTradeCont1(tradeSlotID, invSlotID, itemID, quantity);
	// else use Trade Container 2
	} else {
		trade->updateTradeCont2(tradeSlotID, invSlotID, itemID, quantity);
	}
	exitTradeList();

	schar1->quePacket(CTradeItemAcceptedPacket(itemID, quantity, tradeSlotID, invSlotID));

	if (item != NULL) //Item
		schar2->quePacket(CTradeUpdatePacket(item, quantity, tradeSlotID));
	else // Gil
		schar2->quePacket(CTradeUpdatePacket(quantity, tradeSlotID));
}

void CTradeHandler::removeItemTrade(unsigned char tradeSlotID, CChar * schar1) {
	unsigned int tradeID = 0;
	tradeID = schar1->getTradeID();
	if(tradeID == 0)
		return;

	CTrade * trade = NULL;
	trade = getTrade(tradeID);
	if(trade == NULL)
		return;

	bool isTradeChar1 = false;
	// schar2 is the character that didnt request the update
	CChar * schar2 = NULL;
	if(schar1 == trade->getChar1()) {
		schar2 = trade->getChar2();
		isTradeChar1 = true;
	} else if(schar1 == trade->getChar2()) {
		schar2 = trade->getChar1();
		isTradeChar1 = false;
	}

	if(schar2 == NULL) {
		CConsole::outDebOnly("Character null. Deleting trade.");
		deleteTrade(tradeID);
		return;
	}

	waitTradeList();
	trade->accepted(schar1, false);

	// if schar1 isTradeChar1, then use Trade Container 1
	if(isTradeChar1) {
		trade->updateTradeCont1(tradeSlotID, 0, 0, 0);
	// else use Trade Container 2
	} else {
		trade->updateTradeCont2(tradeSlotID, 0, 0, 0);
	}
	exitTradeList();

	CItem * item = new CItem;
	// Empty item
	item->setId(0xFFFF);
	schar1->quePacket(CTradeItemAcceptedPacket(0xFFFF, 0, tradeSlotID, 0));
	schar2->quePacket(CTradeUpdatePacket(item, 0, tradeSlotID));

}

void CTradeHandler::completeTrade(int tradeID) {
	CTrade * trade;
	trade = getTrade(tradeID);
	if(trade != NULL) {
		CChar * schar1 = trade->getChar1();
		CChar * schar2 = trade->getChar2();
		if( (schar1 == NULL) || (schar2 == NULL) ) {
			CConsole::outDebOnly("Character null. Trade invalid.");
			deleteTrade(tradeID);
			return;
		}
		if( (!trade->isAccepted(schar1)) || (!trade->isAccepted(schar2)) ) {
			CConsole::outDebOnly("Trade not accepted by both characters.");
			return;
		}
		CTradeContainer * tradeCont1 = trade->getTradeCont1();
		CTradeContainer * tradeCont2 = trade->getTradeCont2();
		if( (schar1 == NULL) || (schar2 == NULL) ) {
			CConsole::outDebOnly("Trade container null. Trade invalid.");
			deleteTrade(tradeID);
			return;
		} 

		if(tradeCont2->getSlotCount() > schar1->getFreeSlotCount(LOC_INVENTORY)) {
			CConsole::outDebOnly("%s inventory full.",schar1->getName());
			schar1->quePacket(CTradeActionPacket(schar2, 0x01));
			schar2->quePacket(CTradeActionPacket(schar1, 0x01));
			deleteTrade(tradeID);
			return;
		}
		if(tradeCont1->getSlotCount() > schar2->getFreeSlotCount(LOC_INVENTORY)) {
			CConsole::outDebOnly("%s inventory full.",schar2->getName());
			schar1->quePacket(CTradeActionPacket(schar2, 0x01));
			schar2->quePacket(CTradeActionPacket(schar1, 0x01));
			deleteTrade(tradeID);
			return;
		}

		doTrade(schar1, schar2, tradeCont1);
		doTrade(schar2, schar1, tradeCont2);

		schar1->quePacket(CTradeActionPacket(schar2, 0x09));
		schar2->quePacket(CTradeActionPacket(schar1, 0x09));
		CConsole::outDebOnly("Trade %d completed.",tradeID);

		// Remove trade from trade list
		deleteTrade(tradeID);
	}
}

void CTradeHandler::exitTradeList(void)
{
	tradeListSemaphore->unlock();
	return;
}

void CTradeHandler::waitTradeList(void) 
{
	tradeListSemaphore->lock();
	return;
}

void CTradeHandler::doTrade(CChar * schar1, CChar * schar2, CTradeContainer * trade) 
{
	if (trade->getGil()!=0)
	{
		CConsole::outDebOnly("Sending %u Gil from %s to %s",trade->getGil(), schar1->getName(), schar2->getName());
		schar1->removeGil(trade->getGil());
		schar2->addGil(trade->getGil());
	}
	for(unsigned char i=1;i<=8;i++) 
	{
		unsigned char invSlotID = trade->getInvSlotID(i);
		unsigned long quantity = trade->getQuantity(i);
		CItem * item = schar1->getItem(LOC_INVENTORY, invSlotID);
		if(item != NULL) {
			if(item->getQuantity() == quantity) {
				unsigned char newSlotID = 0x00;
				newSlotID = schar2->inventory->moveItemTo(item);
				schar2->updateInvStor(LOC_INVENTORY, newSlotID);
				schar2->sendItemInvStor(LOC_INVENTORY, newSlotID);

				schar1->inventory->setItem(invSlotID, NULL);
				schar1->updateInvStor(LOC_INVENTORY, invSlotID);
				schar1->sendUpdateInvStor(invSlotID, LOC_INVENTORY, 0);
			} else {
				unsigned char newSlotID = 0x00;
				newSlotID = schar2->inventory->addItem(item->getId(), quantity);
				schar2->updateInvStor(LOC_INVENTORY, newSlotID);
				schar2->sendItemInvStor(LOC_INVENTORY, newSlotID);

				schar1->removeItem(invSlotID, LOC_INVENTORY, quantity);
				schar1->updateInvStor(LOC_INVENTORY, invSlotID);
				schar1->sendUpdateInvStor(invSlotID, LOC_INVENTORY, item->getQuantity());
			}
		}
	}
	schar1->sendFinishInvStor();
	schar2->sendFinishInvStor();
}